/**
 * Copyright (C) 2007-2009 Alexis Kinsella - http://www.helyx.org - <Helyx.org>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.helyx.gae.util.stream;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.helyx.gae.util.stream.exception.HttpAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class HttpConnectionInputStreamProvider implements InputStreamProvider {
	
	private static final String HTTP_HEADER_FIELD_USER_AGENT = "User-Agent";
	private static final String HTTP_HEADER_FIELD_LOCATION = "Location";

	private static final String DEFAULT_USER_AGENT = "GAE/GetABike/1.0";
	
	private static final Logger LOGGER = LoggerFactory.getLogger(HttpConnectionInputStreamProvider.class);

	private String url;
	private ClientConnectionManager clientConnectionManager;
	private InputStream inputStream;
	private boolean isInit = false;
	private boolean isCreated = false;
	private boolean isDisposed = false;
	private Map<String, String> httpHeaders;
	
	public HttpConnectionInputStreamProvider(ClientConnectionManager clientConnectionManager, String url) {
		this(clientConnectionManager, url, true, null);
	}
	
	public HttpConnectionInputStreamProvider(ClientConnectionManager clientConnectionManager, String url, boolean throwExceptionOnBadReturnCode) {
		this(clientConnectionManager, url, throwExceptionOnBadReturnCode, null);
	}
	
	public HttpConnectionInputStreamProvider(ClientConnectionManager clientConnectionManager, String url, Map<String, String> httpHeaders) {
		this(clientConnectionManager, url, true, httpHeaders);
	}
	
	public HttpConnectionInputStreamProvider(ClientConnectionManager clientConnectionManager, String url, boolean throwExceptionOnBadReturnCode, Map<String, String> httpHeaders) {
		super();
		this.clientConnectionManager = clientConnectionManager;
		this.url = url;
		this.httpHeaders = httpHeaders;
	}

	public InputStream createInputStream() throws IOException {
		return createInputStream(false, null);
	}
	
	public InputStream createInputStream(Map<String, String> headers) throws IOException {
		return createInputStream(false, headers);
	}
	
	public InputStream createInputStream(boolean buffered) throws IOException {
		return createInputStream(buffered, null);
	}

	public synchronized InputStream createInputStream(boolean buffered, Map<String, String> httpHeaders) throws IOException {

		if (isInit) {
			throw new RuntimeException("InputStream already Opened");
		}
		isInit = true;
		Map<String, String> tmpHttpHeaders = mergeHttpHeaders(httpHeaders);
		
		HttpParams httpParams = new BasicHttpParams();
		HttpClient httpclient = new DefaultHttpClient(clientConnectionManager, httpParams);
		HttpRequestBase httpRequest = createHttpRequest(tmpHttpHeaders);
		HttpResponse httpResponse = httpclient.execute(httpRequest);
        
        LOGGER.debug("About to open connection");
        
        int redirectCount = 0;
        int httpResponseCode = httpResponse.getStatusLine().getStatusCode();
        while ( httpResponseCode == HttpStatus.SC_MOVED_PERMANENTLY ||
				httpResponseCode == HttpStatus.SC_MOVED_TEMPORARILY ||
				httpResponseCode == HttpStatus.SC_SEE_OTHER ) {
			redirectCount++;
			if (redirectCount > 3) {
				String urlRedirection = httpResponse.getFirstHeader(HTTP_HEADER_FIELD_LOCATION).getValue();
				// Release here httpGet Resources
				throw new HttpAccessException(httpResponseCode, "3rd try - Http Response Code: '" + httpResponseCode + "', Actual URL redirection: '" + urlRedirection + "'");
			}
			String urlRedirection = httpResponse.getFirstHeader(HTTP_HEADER_FIELD_LOCATION).getValue();
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug(httpResponseCode + " - Redirecting connection to following url: '" + urlRedirection + "'");
			}
			// Release here httpGet Resources
			httpRequest = new HttpGet(url);
			setHttpHeaders(httpRequest, tmpHttpHeaders);
			httpResponse = httpclient.execute(httpRequest);

	        httpResponseCode = httpResponse.getStatusLine().getStatusCode();
	 	}	
		
		httpResponseCode = httpResponse.getStatusLine().getStatusCode();
		
		if (httpResponseCode != HttpStatus.SC_OK) {
			
			String httpResponseMessage = httpResponse.getStatusLine().getReasonPhrase();
			String errorMessage = httpResponseCode + " - "+ httpResponseMessage;
			LOGGER.debug("Closing connection: "  + errorMessage);
		    throw new HttpAccessException(httpResponseCode, errorMessage);	 
		}

		HttpEntity entity = httpResponse.getEntity();
		if (entity == null) {
			throw new HttpAccessException(httpResponseCode, "Unexpected exception: " + httpResponse.getStatusLine().getReasonPhrase());
		}

		inputStream = entity.getContent();
		
		if (buffered) {
			inputStream = new BufferedInputStream(inputStream);
		}
		
		isCreated = true;
		
		return inputStream;
	}

	private HttpRequestBase createHttpRequest(Map<String, String> tmpHttpHeaders) throws IOException {
		HttpRequestBase httpRequest = new HttpGet(url);
		
		setHttpHeaders(httpRequest, tmpHttpHeaders);		
		
		return httpRequest;
	}

	private Map<String, String> mergeHttpHeaders(Map<String, String> httpHeaders) {
		Map<String, String> tmpHttpHeaders = new HashMap<String, String>();
		if (this.httpHeaders != null) {
            tmpHttpHeaders.putAll(this.httpHeaders);         
		}
		if (httpHeaders != null) {
            tmpHttpHeaders.putAll(httpHeaders);         
		}
		
		return tmpHttpHeaders;
	}

	private void setHttpHeaders(HttpRequestBase httpRequest, Map<String, String> headers) throws IOException {
        if (headers != null) {
            for (Entry<String, String> entry : headers.entrySet()) {
            	String key = entry.getKey();
            	String value = entry.getValue();
            	httpRequest.addHeader(key, value);
            }
        }

        if (headers == null || !headers.containsKey(HTTP_HEADER_FIELD_USER_AGENT)) {
        	httpRequest.addHeader(HTTP_HEADER_FIELD_USER_AGENT, DEFAULT_USER_AGENT);
        }
    }

	public void dispose() throws IOException {
		try {
			if (inputStream != null) {
				inputStream.close();
			}
		}
		finally {
			isDisposed = true;
			// Close HttpClient resources
		}
	}

	public boolean isCreated() {
		return isCreated;
	}

	public boolean isDisposed() {
		return isDisposed;
	}

}
